home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / python-cupshelpers / cupshelpers / ppds.py < prev   
Encoding:
Python Source  |  2009-05-05  |  37.8 KB  |  1,050 lines

  1. #!/usr/bin/env python
  2.  
  3. ## system-config-printer
  4.  
  5. ## Copyright (C) 2006, 2007, 2008 Red Hat, Inc.
  6. ## Copyright (C) 2006 Florian Festi <ffesti@redhat.com>
  7. ## Copyright (C) 2006, 2007, 2008 Tim Waugh <twaugh@redhat.com>
  8.  
  9. ## This program is free software; you can redistribute it and/or modify
  10. ## it under the terms of the GNU General Public License as published by
  11. ## the Free Software Foundation; either version 2 of the License, or
  12. ## (at your option) any later version.
  13.  
  14. ## This program is distributed in the hope that it will be useful,
  15. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. ## GNU General Public License for more details.
  18.  
  19. ## You should have received a copy of the GNU General Public License
  20. ## along with this program; if not, write to the Free Software
  21. ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  22.  
  23. import cups
  24. from .cupshelpers import parseDeviceID
  25. import string
  26. import locale
  27. import os.path
  28. import re
  29. from . import _debugprint
  30.  
  31. __all__ = ['ppdMakeModelSplit',
  32.            'PPDs']
  33.  
  34. def ppdMakeModelSplit (ppd_make_and_model):
  35.     """
  36.     Split a ppd-make-and-model string into a canonical make and model pair.
  37.  
  38.     @type ppd_make_and_model: string
  39.     @param ppd_make_and_model: IPP ppd-make-and-model attribute
  40.     @return: a string pair representing the make and the model
  41.     """
  42.  
  43.     # If the string starts with a known model name (like "LaserJet") assume
  44.     # that the manufacturer name is missing and add the manufacturer name
  45.     # corresponding to the model name
  46.     if re.search ("^\s*(deskjet|dj\b|dj\d|laserjet|lj\b|color\s*laserjet|color\s*lj\b|designjet|officejet|oj\b|photosmart|ps\b|psc)", \
  47.                       ppd_make_and_model, re.I) or \
  48.        re.search ("(edgeline)", ppd_make_and_model, re.I):
  49.         make = "HP"
  50.         model = ppd_make_and_model
  51.     elif re.search ("^\s*(stylus|aculaser)", \
  52.                       ppd_make_and_model, re.I):
  53.         make = "Epson"
  54.         model = ppd_make_and_model
  55.     elif re.search ("^\s*(stylewriter|imagewriter|deskwriter|laserwriter)", \
  56.                       ppd_make_and_model, re.I):
  57.         make = "Apple"
  58.         model = ppd_make_and_model
  59.     elif re.search ("^\s*(pixus|pixma|selphy|imagerunner|\bbjc\b|\bbj\b|\blbp\b)",\
  60.                       ppd_make_and_model, re.I):
  61.         make = "Canon"
  62.         model = ppd_make_and_model
  63.     elif re.search ("^\s*(\bhl\b|\bdcp\b|\bmfc\b)", \
  64.                       ppd_make_and_model, re.I):
  65.         make = "Brother"
  66.         model = ppd_make_and_model
  67.     elif re.search ("^\s*(docuprint|docupage|phaser|workcentre|homecentre)", \
  68.                       ppd_make_and_model, re.I):
  69.         make = "Xerox"
  70.         model = ppd_make_and_model
  71.     elif re.search ("^\s*(optra|(color\s*|)jetprinter)", \
  72.                       ppd_make_and_model, re.I):
  73.         make = "Lexmark"
  74.         model = ppd_make_and_model
  75.     elif re.search ("^\s*(magicolor|pageworks|pagepro)", \
  76.                       ppd_make_and_model, re.I):
  77.         make = "KONICA MINOLTA"
  78.         model = ppd_make_and_model
  79.     elif re.search ("^\s*(aficio)", \
  80.                       ppd_make_and_model, re.I):
  81.         make = "Ricoh"
  82.         model = ppd_make_and_model
  83.     elif re.search ("^\s*(varioprint)", \
  84.                       ppd_make_and_model, re.I):
  85.         make = "Oce"
  86.         model = ppd_make_and_model
  87.     elif re.search ("^\s*(okipage|microline)", \
  88.                       ppd_make_and_model, re.I):
  89.         make = "Oki"
  90.         model = ppd_make_and_model
  91.     elif re.search ("^\s*(konica[\s_-]*minolta)", \
  92.                       ppd_make_and_model, re.I):
  93.         make = "KONICA MINOLTA"
  94.         model = ppd_make_and_model
  95.         model = re.sub ("(?i)KONICA[\s_-]*MINOLTA\s*", "", model, 1)
  96.     elif re.search("turboprint", ppd_make_and_model, re.I):
  97.         # Support for Turboprint PPDs
  98.         t = ppd_make_and_model.find (" TurboPrint")
  99.         if t != -1:
  100.             ppd_make_and_model = ppd_make_and_model[:t]
  101.         try:
  102.             make, model = ppd_make_and_model.split("_", 1)
  103.         except:
  104.             make = ppd_make_and_model
  105.             model = ''
  106.         make = re.sub (r"(?<=[a-z])(?=[0-9])", " ", make)
  107.         make = re.sub (r"(?<=[a-z])(?=[A-Z])", " ", make)
  108.         model = re.sub (r"(?<=[a-z])(?=[0-9])", " ", model)
  109.         model = re.sub (r"(?<=[a-z])(?=[A-Z])", " ", model)
  110.         model = re.sub (r" Jet", "Jet", model)
  111.         model = re.sub (r"Photo Smart", "PhotoSmart", model)
  112.     else:
  113.         try:
  114.             make, model = ppd_make_and_model.split(" ", 1)
  115.         except:
  116.             make = ppd_make_and_model
  117.             model = ''
  118.  
  119.     def strip_suffix (model, suffix):
  120.         if model.endswith (suffix):
  121.             return model[:-len(suffix)]
  122.         return model
  123.  
  124.     # Model names do not contain a comma, truncate all from the
  125.     # comma on
  126.     c = model.find (",")
  127.     if c != -1:
  128.         model = model[:c]
  129.  
  130.     # HP PPDs give NickNames like:
  131.     # *NickName: "HP LaserJet 4 Plus v2013.111 Postscript (recommended)"
  132.     # Find the version number.
  133.     v = model.find (" v")
  134.     if v != -1 and (model[v + 2].isdigit () or
  135.                     (model[v + 2] == '.' and
  136.                      model[v + 3].isdigit ())):
  137.         # Truncate at that point.
  138.         model = model[:v]
  139.  
  140.     h = model.find (" hpijs")
  141.     if h != -1:
  142.         model = model[:h]
  143.  
  144.     f = model.find (" Foomatic/")
  145.     if f != -1:
  146.         model = model[:f]
  147.  
  148.     # Gutenprint PPDs have NickNames that end:
  149.     # ... - CUPS+Gutenprint v5.0.0
  150.     gutenprint = model.find (" - CUPS+Gutenprint")
  151.     if gutenprint != -1:
  152.         model = model[:gutenprint]
  153.  
  154.     # Gimp-Print PPDs have NickNames that end:
  155.     # ... - CUPS+Gimp-Print v4.2.7
  156.     gimpprint = model.find (" - CUPS+Gimp-Print")
  157.     if gimpprint != -1:
  158.         model = model[:gimpprint]
  159.  
  160.     wth = model.find (" w/")
  161.     if wth != -1:
  162.         model = model[:wth]
  163.  
  164.     make = re.sub (r"(?i)KONICA[\s_-]*MINOLTA", "KONICA MINOLTA", make, 1)
  165.     make = re.sub (r"(?i)HEWLETT[\s_-]PACKARD", "HP", make, 1)
  166.     make = re.sub (r"(?i)Lexmark\s*International", "Lexmark", make, 1)
  167.     model = re.sub (r"(?i)\s*\(recommended\)", "", model)
  168.     model = re.sub (r"(?i)\s*-\s*PostScript\b", "", model)
  169.     model = re.sub (r"(?i)\s*\bseries\b", "", model)
  170.     if make.lower () == "hp":
  171.         model = re.sub (r"(?i)^dj\b", "DeskJet", model)
  172.         model = re.sub (r"(?i)^LJ\b", "LaserJet", model)
  173.         model = re.sub (r"(?i)^OJ\b", "OfficeJet", model)
  174.         model = re.sub (r"(?i)^Color\s*LJ\b", "Color LaserJet", model)
  175.         model = re.sub (r"(?i)^PS\b", "PhotoSmart", model)
  176.     model = re.sub (r"(?i)\s*\bPS[123]?\b", "", model)
  177.     model = re.sub (r"(?i)\s*\bPXL", "", model)
  178.     model = re.sub (r"(?i)[\s_-]+BT\b", "", model)
  179.     model = re.sub (r"(?i)\s*\(Bluetooth\)", "", model)
  180.     model = re.sub (r"(?i)\s*-\s*(RC|Ver(|sion))\s*-*\s*[0-9\.]+", "", model)
  181.     model = re.sub (r"(?i)\s*-\s*(RC|Ver(|sion))\b", "", model)
  182.     model = re.sub (r"(?i)\s*PostScript\s*$", "", model)
  183.     model = re.sub (r"(?i)\s*\(\s*\)", "", model)
  184.     model = re.sub (r"(?i)\s*[\-\/]\s*$", "", model)
  185.  
  186.     for mfr in [ "Apple", "Canon", "Epson", "Lexmark", "Oki" ]:
  187.         if make == mfr.upper ():
  188.             make = mfr
  189.  
  190.     model = model.strip ()
  191.     return (make, model)
  192.  
  193. # Some drivers are just generally better than others.
  194. # Here is the preference list:
  195. DRIVER_TYPE_DOWNLOADED_NOW = 5
  196. DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT = 8
  197. DRIVER_TYPE_VENDOR = 10
  198. DRIVER_TYPE_FOOMATIC_RECOMMENDED_POSTSCRIPT = 15
  199. DRIVER_TYPE_FOOMATIC_HPIJS_ON_HP = 17
  200. DRIVER_TYPE_GUTENPRINT_NATIVE_SIMPLIFIED = 20
  201. DRIVER_TYPE_GUTENPRINT_NATIVE = 25
  202. DRIVER_TYPE_SPLIX = 27
  203. DRIVER_TYPE_FOOMATIC_PS = 30
  204. DRIVER_TYPE_FOOMATIC_HPIJS = 40
  205. DRIVER_TYPE_FOOMATIC_GUTENPRINT_SIMPLIFIED = 50
  206. DRIVER_TYPE_FOOMATIC_GUTENPRINT = 60
  207. DRIVER_TYPE_FOOMATIC = 70
  208. DRIVER_TYPE_CUPS = 80
  209. DRIVER_TYPE_FOOMATIC_GENERIC = 90
  210. DRIVER_TYPE_3RD_PARTY_NONFREE = 95
  211. DRIVER_DOES_NOT_WORK = 999
  212. def _getDriverType (ppdname, ppds=None):
  213.     """Decides which of the above types ppdname is."""
  214.     if ppdname.find ("turboprint") != -1:
  215.         return DRIVER_TYPE_3RD_PARTY_NONFREE
  216.     if ppdname.find ("gutenprint") != -1:
  217.         if (ppdname.find ("/simple/") != -1 or
  218.             ppdname.find (".sim-") != -1):
  219.             return DRIVER_TYPE_GUTENPRINT_NATIVE_SIMPLIFIED
  220.         else:
  221.             return DRIVER_TYPE_GUTENPRINT_NATIVE
  222.     if ppdname.find ("splix")!= -1:
  223.         return DRIVER_TYPE_SPLIX
  224.     if (ppdname.find (":") == -1 and
  225.         ppdname.find ("/cups-included/") != -1):
  226.         return DRIVER_TYPE_CUPS
  227.     if ppdname.startswith ("foomatic:"):
  228.         # Foomatic (generated) -- but which driver?
  229.         if ppdname.find ("Generic")!= -1:
  230.             return DRIVER_TYPE_FOOMATIC_GENERIC
  231.         if (ppds != None and
  232.             ppds.getInfoFromPPDName (ppdname).\
  233.             get ('ppd-make-and-model', '').find ("(recommended)") != -1):
  234.             if ppds.getInfoFromPPDName (ppdname).\
  235.                get ('ppd-make-and-model', '').find ("Postscript") != -1:
  236.                 return DRIVER_TYPE_FOOMATIC_RECOMMENDED_POSTSCRIPT
  237.             else:
  238.                 return DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT
  239.         if ppdname.find ("-Postscript")!= -1:
  240.             return DRIVER_TYPE_FOOMATIC_PS
  241.         if ppdname.find ("-hpijs") != -1:
  242.             if ppdname.find ("hpijs-rss") == -1:
  243.                 return DRIVER_TYPE_FOOMATIC_HPIJS
  244.         if ppdname.find ("-gutenprint") != -1:
  245.             if ppdname.find ("-simplified")!= -1:
  246.                 return DRIVER_TYPE_FOOMATIC_GUTENPRINT_SIMPLIFIED
  247.             return DRIVER_TYPE_FOOMATIC_GUTENPRINT
  248.         return DRIVER_TYPE_FOOMATIC
  249.     if ppdname.find ("-hpijs") != -1:
  250.         if ppdname.find ("hpijs-rss") == -1:
  251.             return DRIVER_TYPE_FOOMATIC_HPIJS
  252.     # Anything else should be a vendor's PPD.
  253.     return DRIVER_TYPE_VENDOR # vendor's own
  254.  
  255.  
  256. class PPDs:
  257.     """
  258.     This class is for handling the list of PPDs returned by CUPS.  It
  259.     indexes by PPD name and device ID, filters by natural language so
  260.     that foreign-language PPDs are not included, and sorts by driver
  261.     type.  If an exactly-matching PPD is not available, it can
  262.     substitute with a PPD for a similar model or for a generic driver.
  263.     """
  264.  
  265.     # Status of match.
  266.     STATUS_SUCCESS = 0
  267.     STATUS_MODEL_MISMATCH = 1
  268.     STATUS_GENERIC_DRIVER = 2
  269.     STATUS_NO_DRIVER = 3
  270.  
  271.     def __init__ (self, ppds, language=None):
  272.         """
  273.         @type ppds: dict
  274.         @param ppds: dict of PPDs as returned by cups.Connection.getPPDs()
  275.  
  276.         @type language: string
  277.     @param language: language name, as given by the first element
  278.         of the pair returned by locale.getlocale()
  279.         """
  280.         self.ppds = ppds.copy ()
  281.         self.makes = None
  282.         self.ids = None
  283.  
  284.         if (language == None or
  285.             language == "C" or
  286.             language == "POSIX"):
  287.             language = "en_US"
  288.  
  289.         u = language.find ("_")
  290.         if u != -1:
  291.             short_language = language[:u]
  292.         else:
  293.             short_language = language
  294.  
  295.         to_remove = []
  296.         for ppdname, ppddict in self.ppds.iteritems ():
  297.             try:
  298.                 natural_language = ppddict['ppd-natural-language']
  299.             except KeyError:
  300.                 continue
  301.  
  302.             if natural_language == "en":
  303.                 # Some manufacturer's PPDs are only available in this
  304.                 # language, so always let them though.
  305.                 continue
  306.  
  307.             if natural_language == language:
  308.                 continue
  309.  
  310.             if natural_language == short_language:
  311.                 continue
  312.  
  313.             to_remove.append (ppdname)
  314.  
  315.         for ppdname in to_remove:
  316.             del self.ppds[ppdname]
  317.  
  318.         # CUPS sets the 'raw' model's ppd-make-and-model to 'Raw Queue'
  319.         # which unfortunately then appears as manufacturer Raw and
  320.         # model Queue.  Use 'Generic' for this model.
  321.         if self.ppds.has_key ('raw'):
  322.             makemodel = self.ppds['raw']['ppd-make-and-model']
  323.             if not makemodel.startswith ("Generic "):
  324.                 self.ppds['raw']['ppd-make-and-model'] = "Generic " + makemodel
  325.  
  326.     def getMakes (self):
  327.         """
  328.     @returns: a list of strings representing makes, sorted according
  329.         to the current locale
  330.     """
  331.         self._init_makes ()
  332.         makes_list = self.makes.keys ()
  333.         makes_list.sort (locale.strcoll)
  334.         try:
  335.             # "Generic" should be listed first.
  336.             makes_list.remove ("Generic")
  337.             makes_list.insert (0, "Generic")
  338.         except ValueError:
  339.             pass
  340.         return makes_list
  341.  
  342.     def getModels (self, make):
  343.         """
  344.     @returns: a list of strings representing models, sorted using
  345.     cups.modelSort()
  346.     """
  347.         self._init_makes ()
  348.         try:
  349.             models_list = self.makes[make].keys ()
  350.         except KeyError:
  351.             return []
  352.         models_list.sort (key=unicode.lower, cmp=cups.modelSort)
  353.         return models_list
  354.  
  355.     def getInfoFromModel (self, make, model):
  356.         """
  357.     Obtain a list of PPDs that are suitable for use with a
  358.         particular printer model, given its make and model name.
  359.  
  360.     @returns: a dict, indexed by ppd-name, of dicts representing
  361.         PPDs (as given by cups.Connection.getPPDs)
  362.     """
  363.         self._init_makes ()
  364.         try:
  365.             return self.makes[make][model]
  366.         except KeyError:
  367.             return {}
  368.  
  369.     def getInfoFromPPDName (self, ppdname):
  370.         """
  371.     @returns: a dict representing a PPD, as given by
  372.     cups.Connection.getPPDs
  373.     """
  374.         return self.ppds[ppdname]
  375.  
  376.     def orderPPDNamesByPreference (self, ppdnamelist=[],
  377.                                    downloadedfiles=[]):
  378.         """
  379.  
  380.     Sort a list of PPD names by (hard-coded) preferred driver
  381.     type.
  382.  
  383.     @param ppdnamelist: PPD names
  384.     @type ppdnamelist: string list
  385.     @returns: string list
  386.     """
  387.         if len (ppdnamelist) < 1:
  388.             return ppdnamelist
  389.  
  390.         dict = self.getInfoFromPPDName (ppdnamelist[0])
  391.         make_model = dict['ppd-make-and-model']
  392.         mfg, mdl = ppdMakeModelSplit (make_model)
  393.         def getDriverTypeWithBias (x, mfg):
  394.             t = _getDriverType (x, ppds=self)
  395.             for file in downloadedfiles:
  396.                 (path, slash, filename) = file.rpartition ("/")
  397.                 (xpath, xslash, xfilename) = x.rpartition ("/")
  398.                 if filename == xfilename:
  399.                     return DRIVER_TYPE_DOWNLOADED_NOW
  400.             if mfg == "HP":
  401.                 if t == DRIVER_TYPE_FOOMATIC_HPIJS:
  402.                     # Prefer HPIJS for HP devices.
  403.                     t = DRIVER_TYPE_FOOMATIC_HPIJS_ON_HP
  404.                     # For HP LaserJet 12xx/13xx prefer HPIJS over
  405.                     # PostScript, as they do not have enough memory
  406.                     # to render complex graphics with their on-board
  407.                     # PostScript interpreter
  408.                     if re.search(r"(?i)HP[-_]LaserJet_1[23]\d\d", x):
  409.                         t = DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT
  410.             return t
  411.  
  412.         def sort_ppdnames (a, b):
  413.             ta = getDriverTypeWithBias (a, mfg)
  414.             tb = getDriverTypeWithBias (b, mfg)
  415.             if ta != tb:
  416.                 if tb < ta:
  417.                     return 1
  418.                 else:
  419.                     return -1
  420.  
  421.             # Prefer C locale localized PPDs to other languages, just
  422.             # because we don't know the user's locale.  This only
  423.             # applies to PPDs provided by gutenprint 5.0; in 5.2 the
  424.             # PPDs are localized properly.
  425.             def is_C_locale (x):
  426.                 try:
  427.                     while x:
  428.                         i = x.find ("C")
  429.                         if i == -1:
  430.                             return False
  431.                         lword = False
  432.                         if i == 0:
  433.                             lword = True
  434.                         elif x[i - 1] not in string.letters:
  435.                             lword = True
  436.  
  437.                         if lword:
  438.                             rword = False
  439.                             if i == (len (x) - 1):
  440.                                 rword = True
  441.                             elif x[i + 1] not in string.letters:
  442.                                 rword = True
  443.                             if rword:
  444.                                 return True
  445.                         
  446.                         x = x[i + 1:]
  447.                 except UnicodeDecodeError:
  448.                     return False
  449.  
  450.             ca = is_C_locale (a)
  451.             cb = is_C_locale (b)
  452.             if ca != cb:
  453.                 # If they compare equal stringwise up to "C", sort.
  454.                 if ca:
  455.                     l = a.find ("C")
  456.                 else:
  457.                     l = b.find ("C")
  458.  
  459.                 if a[:l] == b[:l]:
  460.                     if cb:
  461.                         return 1
  462.                     else:
  463.                         return -1
  464.  
  465.             # String-wise compare.
  466.             if a > b:
  467.                 return 1
  468.             elif a < b:
  469.                 return -1
  470.             return 0
  471.  
  472.         ppdnamelist.sort (sort_ppdnames)
  473.         return ppdnamelist
  474.  
  475.     def getPPDNameFromDeviceID (self, mfg, mdl, description="",
  476.                                 commandsets=[], uri=None,
  477.                                 downloadedfiles=[]):
  478.         """
  479.     Obtain a best-effort PPD match for an IEEE 1284 Device ID.
  480.     The status is one of:
  481.  
  482.       - L{STATUS_SUCCESS}: the match was successful, and an exact
  483.             match was found
  484.  
  485.       - L{STATUS_MODEL_MISMATCH}: a similar match was found, but
  486.             the model name does not exactly match
  487.  
  488.       - L{STATUS_GENERIC_DRIVER}: no match was found, but a
  489.             generic driver is available that can drive this device
  490.             according to its command set list
  491.  
  492.       - L{STATUS_NO_DRIVER}: no match was found at all, and the
  493.             returned PPD name is a last resort
  494.  
  495.     @param mfg: MFG or MANUFACTURER field
  496.     @type mfg: string
  497.     @param mdl: MDL or MODEL field
  498.     @type mdl: string
  499.     @param description: DES or DESCRIPTION field, optional
  500.     @type description: string
  501.     @param commandsets: CMD or COMMANDSET field, optional
  502.     @type commandsets: string
  503.     @param uri: device URI, optional (only needed for debugging)
  504.     @type uri: string
  505.     @returns: an integer,string pair of (status,ppd-name)
  506.     """
  507.         _debugprint ("\n%s %s" % (mfg, mdl))
  508.         self._init_ids ()
  509.         # Consider "HP" and "Hewlett-Packard" as equal, as the ID returned
  510.         # by the CUPS "usb" backend and HPLIP's "hp" backend are different
  511.         if mfg.lower () == "hewlett-packard": mfg = "HP"
  512.         ppdnamelist = []
  513.         id_matched = False
  514.         try:
  515.             ppdnamelist = self.ids[mfg.lower ()][mdl.lower ()]
  516.             status = self.STATUS_SUCCESS
  517.             id_matched = True
  518.         except KeyError:
  519.             pass
  520.         # The HPLIP PPDs have incorrect IDs
  521.         if mfg.lower () == "hp":
  522.             try:
  523.                 ppdnamelist += self.ids['hp'][mdl.lower ()]
  524.                 status = self.STATUS_SUCCESS
  525.                 id_matched = True
  526.             except KeyError:
  527.                 pass
  528.             try:
  529.                 ppdnamelist += self.ids['hp'][mdl.lower ().replace (" ", "_")]
  530.                 status = self.STATUS_SUCCESS
  531.                 id_matched = True
  532.             except KeyError:
  533.                 pass
  534.         if mfg.lower () == "apollo":
  535.             try:
  536.                 ppdnamelist += self.ids['apollo'][mdl.lower ()]
  537.                 status = self.STATUS_SUCCESS
  538.                 id_matched = True
  539.             except KeyError:
  540.                 pass
  541.             try:
  542.                 ppdnamelist += self.ids['apollo'][mdl.lower ().replace (" ", "_")]
  543.                 status = self.STATUS_SUCCESS
  544.                 id_matched = True
  545.             except KeyError:
  546.                 pass
  547.  
  548.         _debugprint ("Trying make/model names")
  549.         mfgl = mfg.lower ()
  550.         mdls = None
  551.         self._init_makes ()
  552.         for attempt in range (2):
  553.             for (make, models) in self.makes.iteritems ():
  554.                 if make.lower () == mfgl:
  555.                     mdls = models
  556.                     break
  557.  
  558.             # Try again with replacements.
  559.             if mfgl == "hewlett-packard":
  560.                 mfgl = "hp"
  561.             elif mfgl == "lexmark international":
  562.                 mfgl = "lexmark"
  563.             elif mfgl == "":
  564.                 (tmp1, tmp2) = ppdMakeModelSplit (mdl)
  565.                 mfgl = tmp1.lower ()
  566.  
  567.         # Remove manufacturer name from model field
  568.         ppdnamelist2 = None
  569.         if mdl.startswith (mfg + ' '):
  570.             mdl = mdl[len (mfg) + 1:]
  571.         if mdl.startswith ('Hewlett-Packard '):
  572.             mdl = mdl[16:]
  573.         if mdl.startswith ('HP '):
  574.             mdl = mdl[3:]
  575.         if mdls:
  576.             for (model, ppdnames) in mdls.iteritems ():
  577.                 if model.lower () == mdl.lower ():
  578.                     ppdnamelist2 = ppdnames.keys ()
  579.                     status = self.STATUS_SUCCESS
  580.                     break
  581.             if not ppdnamelist2:
  582.                 # Make use of the model name clean-up in the
  583.                 # ppdMakeModelSplit () function
  584.                 (mfg2, mdl2) = ppdMakeModelSplit (mfg + " " + mdl)
  585.                 for (model, ppdnames) in mdls.iteritems ():
  586.                     if model.lower () == mdl2.lower ():
  587.                         ppdnamelist2 = ppdnames.keys ()
  588.                         status = self.STATUS_SUCCESS
  589.                         break
  590.       
  591.         if ppdnamelist:
  592.             if ppdnamelist2:
  593.                 ppdnamelist = ppdnamelist + ppdnamelist2
  594.         elif ppdnamelist2:
  595.             ppdnamelist = ppdnamelist2
  596.  
  597.         if not ppdnamelist and mdls:
  598.             (s, ppds) = self._findBestMatchPPDs (mdls, mdl)
  599.             if s != self.STATUS_NO_DRIVER:
  600.                 status = s
  601.                 ppdnamelist = ppds
  602.  
  603.         if not ppdnamelist and commandsets:
  604.             if type (commandsets) != list:
  605.                 commandsets = commandsets.split (',')
  606.  
  607.             generic = self._getPPDNameFromCommandSet (commandsets)
  608.             if generic:
  609.                 status = self.STATUS_GENERIC_DRIVER
  610.                 ppdnamelist = generic
  611.  
  612.         if not ppdnamelist:
  613.             _debugprint ("Text-only fallback")
  614.             status = self.STATUS_NO_DRIVER
  615.             ppdnamelist = ["textonly.ppd"]
  616.             tppdfound = 0
  617.             for ppdpath in self.ppds.keys ():
  618.                 if ppdpath.endswith (ppdnamelist[0]):
  619.                     tppdfound = 1
  620.                     ppdnamelist = [ppdpath]
  621.                     break
  622.             if tppdfound == 0:
  623.                 _debugprint ("No text-only driver?!  Using postscript.ppd")
  624.                 ppdnamelist = ["postscript.ppd"]
  625.                 psppdfound = 0
  626.                 for ppdpath in self.ppds.keys ():
  627.                     if ppdpath.endswith (ppdnamelist[0]):
  628.                         psppdfound = 1
  629.                         ppdnamelist = [ppdpath]
  630.                         break
  631.                 if psppdfound == 0:
  632.                     _debugprint ("No postscript.ppd; choosing any")
  633.                     ppdnamelist = [self.ppds.keys ()[0]]
  634.  
  635.         if id_matched:
  636.             _debugprint ("Checking DES field")
  637.             inexact = set()
  638.             if description:
  639.                 for ppdname in ppdnamelist:
  640.                     if ppdname.find ("hpijs"): continue
  641.                     ppddict = self.ppds[ppdname]
  642.                     id = ppddict['ppd-device-id']
  643.                     if not id: continue
  644.                     # Fetch description field.
  645.                     id_dict = parseDeviceID (id)
  646.                     if id_dict["DES"] != description:
  647.                         inexact.add (ppdname)
  648.  
  649.             exact = set (ppdnamelist).difference (inexact)
  650.             _debugprint ("discarding: %s" % inexact)
  651.             if len (exact) >= 1:
  652.                 ppdnamelist = list (exact)
  653.  
  654.         # We've got a set of PPDs, any of which will drive the device.
  655.         # Now we have to choose the "best" one.  This is quite tricky
  656.         # to decide, so let's sort them in order of preference and
  657.         # take the first.
  658.         ppdnamelist = self.orderPPDNamesByPreference (ppdnamelist,
  659.                                                       downloadedfiles)
  660.         _debugprint ("Found PPDs: %s" % str (ppdnamelist))
  661.  
  662.         if not id_matched:
  663.             sanitised_uri = re.sub (pattern="//[^@]*@/?", repl="//",
  664.                                     string=str (uri))
  665.             print "No ID match for device %s:" % sanitised_uri
  666.             print "  <manufacturer>%s</manufacturer>" % mfg
  667.             print "  <model>%s</model>" % mdl
  668.             print "  <description>%s</description>" % description
  669.             try:
  670.                 cmd = reduce (lambda x, y: x + ","+ y, commandsets)
  671.             except TypeError:
  672.                 cmd = ""
  673.  
  674.             print "  <commandset>%s</commandset>" % cmd
  675.             print "Using %s" % ppdnamelist[0]
  676.  
  677.         return (status, ppdnamelist[0])
  678.  
  679.     def _findBestMatchPPDs (self, mdls, mdl):
  680.         """
  681.         Find the best-matching PPDs based on the MDL Device ID.
  682.         This function could be made a lot smarter.
  683.         """
  684.  
  685.         _debugprint ("Trying best match")
  686.         mdll = mdl.lower ()
  687.         if mdll.endswith (" series"):
  688.             # Strip " series" from the end of the MDL field.
  689.             mdll = mdll[:-7]
  690.             mdl = mdl[:-7]
  691.         best_mdl = None
  692.         best_matchlen = 0
  693.         mdlnames = mdls.keys ()
  694.  
  695.         # Perform a case-insensitive model sort on the names.
  696.         mdlnamesl = map (lambda x: (x, x.lower()), mdlnames)
  697.         mdlnamesl.append ((mdl, mdll))
  698.         mdlnamesl.sort (lambda x, y: cups.modelSort(x[1], y[1]))
  699.         i = mdlnamesl.index ((mdl, mdll))
  700.         candidates = [mdlnamesl[i - 1]]
  701.         if i + 1 < len (mdlnamesl):
  702.             candidates.append (mdlnamesl[i + 1])
  703.             _debugprint (candidates[0][0] + " <= " + mdl + " <= " +
  704.                         candidates[1][0])
  705.         else:
  706.             _debugprint (candidates[0][0] + " <= " + mdl)
  707.  
  708.         # Look at the models immediately before and after ours in the
  709.         # sorted list, and pick the one with the longest initial match.
  710.         for (candidate, candidatel) in candidates:
  711.             prefix = os.path.commonprefix ([candidatel, mdll])
  712.             if len (prefix) > best_matchlen:
  713.                 best_mdl = mdls[candidate].keys ()
  714.                 best_matchlen = len (prefix)
  715.                 _debugprint ("%s: match length %d" % (candidate, best_matchlen))
  716.  
  717.         # Did we match more than half of the model name?
  718.         if best_mdl and best_matchlen > (len (mdll) / 2):
  719.             ppdnamelist = best_mdl
  720.             if best_matchlen == len (mdll):
  721.                 status = self.STATUS_SUCCESS
  722.             else:
  723.                 status = self.STATUS_MODEL_MISMATCH
  724.         else:
  725.             status = self.STATUS_NO_DRIVER
  726.             ppdnamelist = None
  727.  
  728.             # Last resort.  Find the "most important" word in the MDL
  729.             # field and look for a match based solely on that.  If
  730.             # there are digits, try lowering the number of
  731.             # significant figures.
  732.             mdlnames.sort (cups.modelSort)
  733.             mdlitems = map (lambda x: (x.lower (), mdls[x]), mdlnames)
  734.             modelid = None
  735.             for word in mdll.split (' '):
  736.                 if modelid == None:
  737.                     modelid = word
  738.  
  739.                 have_digits = False
  740.                 for i in range (len (word)):
  741.                     if word[i].isdigit ():
  742.                         have_digits = True
  743.                         break
  744.  
  745.                 if have_digits:
  746.                     modelid = word
  747.                     break
  748.  
  749.             digits = 0
  750.             digits_start = -1
  751.             digits_end = -1
  752.             for i in range (len (modelid)):
  753.                 if modelid[i].isdigit ():
  754.                     if digits_start == -1:
  755.                         digits_start = i
  756.                     digits_end = i
  757.                     digits += 1
  758.                 elif digits_start != -1:
  759.                     break
  760.             digits_end += 1
  761.             modelnumber = 0
  762.             if digits > 0:
  763.                 modelnumber = int (modelid[digits_start:digits_end])
  764.             modelpattern = (modelid[:digits_start] + "%d" +
  765.                             modelid[digits_end:])
  766.             _debugprint ("Searching for model ID '%s', '%s' %% %d" %
  767.                         (modelid, modelpattern, modelnumber))
  768.             ignore_digits = 0
  769.             best_mdl = None
  770.             found = False
  771.             while ignore_digits < digits:
  772.                 div = pow (10, ignore_digits)
  773.                 modelid = modelpattern % ((modelnumber / div) * div)
  774.                 _debugprint ("Ignoring %d of %d digits, trying %s" %
  775.                             (ignore_digits, digits, modelid))
  776.  
  777.                 for (name, ppds) in mdlitems:
  778.                     for word in name.split (' '):
  779.                         if word.lower () == modelid:
  780.                             found = True
  781.                             break
  782.  
  783.                     if found:
  784.                         best_mdl = ppds.keys ()
  785.                         break
  786.  
  787.                 if found:
  788.                     break
  789.  
  790.                 ignore_digits += 1
  791.                 if digits < 2:
  792.                     break
  793.  
  794.             if found:
  795.                 ppdnamelist = best_mdl
  796.                 status = self.STATUS_MODEL_MISMATCH
  797.  
  798.         return (status, ppdnamelist)
  799.  
  800.     def _getPPDNameFromCommandSet (self, commandsets=[]):
  801.         """Return ppd-name list or None, given a list of strings representing
  802.         the command sets supported."""
  803.         try:
  804.             self._init_makes ()
  805.             models = self.makes["Generic"]
  806.         except KeyError:
  807.             return None
  808.  
  809.         def get (*candidates):
  810.             for model in candidates:
  811.                 (s, ppds) = self._findBestMatchPPDs (models, model)
  812.                 if s == self.STATUS_SUCCESS:
  813.                     return ppds
  814.             return None
  815.  
  816.         cmdsets = map (lambda x: x.lower (), commandsets)
  817.         if (("postscript" in cmdsets) or ("postscript2" in cmdsets) or
  818.             ("postscript level 2 emulation" in cmdsets)):
  819.             return get ("PostScript Printer")
  820.         elif (("pclxl" in cmdsets) or ("pcl-xl" in cmdsets) or
  821.               ("pcl6" in cmdsets) or ("pcl 6 emulation" in cmdsets)):
  822.             return get ("PCL 6/PCL XL Printer")
  823.         elif "pcl5e" in cmdsets:
  824.             return get ("PCL 5e Printer")
  825.         elif "pcl5c" in cmdsets:
  826.             return get ("PCL 5c Printer")
  827.         elif ("pcl5" in cmdsets) or ("pcl 5 emulation" in cmdsets):
  828.             return get ("PCL 5 Printer")
  829.         elif "pcl" in cmdsets:
  830.             return get ("PCL 3 Printer")
  831.         elif (("escpl2" in cmdsets) or ("esc/p2" in cmdsets) or
  832.               ("escp2e" in cmdsets)):
  833.             return get ("ESC/P Dot Matrix Printer")
  834.         return None
  835.  
  836.     def _init_makes (self):
  837.         if self.makes:
  838.             return
  839.  
  840.         makes = {}
  841.         lmakes = {}
  842.         lmodels = {}
  843.         for ppdname, ppddict in self.ppds.iteritems ():
  844.             ppd_make_and_model = ppddict['ppd-make-and-model']
  845.             (make, model) = ppdMakeModelSplit (ppd_make_and_model)
  846.             lmake = make.lower ()
  847.             lmodel = model.lower ()
  848.             if not lmakes.has_key (lmake):
  849.                 lmakes[lmake] = make
  850.                 lmodels[lmake] = {}
  851.                 makes[make] = {}
  852.             else:
  853.                 make = lmakes[lmake]
  854.  
  855.             if not lmodels[lmake].has_key (lmodel):
  856.                 lmodels[lmake][lmodel] = model
  857.                 makes[make][model] = {}
  858.             else:
  859.                 model = lmodels[lmake][lmodel]
  860.  
  861.             makes[make][model][ppdname] = ppddict
  862.  
  863.         self.makes = makes
  864.         self.lmakes = lmakes
  865.         self.lmodels = lmodels
  866.  
  867.     def _init_ids (self):
  868.         if self.ids:
  869.             return
  870.  
  871.         ids = {}
  872.         for ppdname, ppddict in self.ppds.iteritems ():
  873.             if not ppddict.has_key ('ppd-device-id'):
  874.                 continue
  875.             id = ppddict['ppd-device-id']
  876.             if not id:
  877.                 continue
  878.  
  879.             # Fix up broken Kyocera IDs
  880.             v = id.find (":Model")
  881.             if v != -1:
  882.                 id = id[:v] + ';' + id[v + 1:]
  883.  
  884.             id_dict = parseDeviceID (id)
  885.             lmfg = id_dict['MFG'].lower ()
  886.             lmdl = id_dict['MDL'].lower ()
  887.  
  888.             # Consider "HP" and "Hewlett-Packard" as equal, as the ID returned
  889.             # by the CUPS "usb" backend and HPLIP's "hp" backend are different
  890.             if lmfg == "hewlett-packard": lmfg = "hp"
  891.  
  892.             bad = False
  893.             if len (lmfg) == 0:
  894.                 _debugprint ("Missing MFG field for %s" % ppdname)
  895.                 bad = True
  896.             if len (lmdl) == 0:
  897.                 _debugprint ("Missing MDL field for %s" % ppdname)
  898.                 bad = True
  899.             if bad:
  900.                 continue
  901.  
  902.             if not ids.has_key (lmfg):
  903.                 ids[lmfg] = {}
  904.  
  905.             if not ids[lmfg].has_key (lmdl):
  906.                 ids[lmfg][lmdl] = []
  907.  
  908.             ids[lmfg][lmdl].append (ppdname)
  909.  
  910.         self.ids = ids
  911.  
  912. def _show_help():
  913.     print "usage: ppds.py [--deviceid] [--list-models] [--list-ids] [--debug]"
  914.  
  915. def _self_test(argv):
  916.     import sys, getopt
  917.     try:
  918.         opts, args = getopt.gnu_getopt (argv[1:], '',
  919.                                         ['help',
  920.                                          'deviceid',
  921.                                          'list-models',
  922.                                          'list-ids',
  923.                                          'debug'])
  924.     except getopt.GetoptError:
  925.         _show_help()
  926.         sys.exit (1)
  927.  
  928.     stdin_deviceid = False
  929.     list_models = False
  930.     list_ids = False
  931.  
  932.     for opt, optarg in opts:
  933.         if opt == "--help":
  934.             show_help ()
  935.             sys.exit (0)
  936.         if opt == "--deviceid":
  937.             stdin_deviceid = True
  938.         elif opt == "--list-models":
  939.             list_models = True
  940.         elif opt == "--list-ids":
  941.             list_ids = True
  942.  
  943.     picklefile="pickled-ppds"
  944.     import pickle
  945.     try:
  946.         f = open (picklefile, "r")
  947.         cupsppds = pickle.load (f)
  948.     except IOError:
  949.         f = open (picklefile, "w")
  950.         c = cups.Connection ()
  951.         cupsppds = c.getPPDs ()
  952.         pickle.dump (cupsppds, f)
  953.  
  954.     ppds = PPDs (cupsppds)
  955.     makes = ppds.getMakes ()
  956.     models_count = 0
  957.     for make in makes:
  958.         models = ppds.getModels (make)
  959.         models_count += len (models)
  960.         if list_models:
  961.             print make
  962.             for model in models:
  963.                 print "  " + model
  964.     print "%d makes, %d models" % (len (makes), models_count)
  965.     ppds.getPPDNameFromDeviceID ("HP", "PSC 2200 Series")
  966.     makes = ppds.ids.keys ()
  967.     models_count = 0
  968.     for make in makes:
  969.         models = ppds.ids[make]
  970.         models_count += len (models)
  971.         if list_ids:
  972.             print make
  973.             for model in models:
  974.                 print "  %s (%d)" % (model, len (ppds.ids[make][model]))
  975.                 for driver in ppds.ids[make][model]:
  976.                     print "    " + driver
  977.     print "%d ID makes, %d ID models" % (len (makes), models_count)
  978.  
  979.     print "\nID matching tests\n"
  980.  
  981.     idlist = [
  982.         # Format is:
  983.         # (ID string, max status code, expected driver RE match)
  984.  
  985.         # Specific models
  986.         ("MFG:EPSON;CMD:ESCPL2,BDC,D4,D4PX;MDL:Stylus D78;CLS:PRINTER;"
  987.          "DES:EPSON Stylus D78;", 1, 'Epson Stylus D68'),
  988.         ("MFG:Hewlett-Packard;MDL:LaserJet 1200 Series;"
  989.          "CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;", 0, 'HP LaserJet 1200'),
  990.         ("MFG:Hewlett-Packard;MDL:LaserJet 3390 Series;"
  991.          "CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;", 0, 'HP LaserJet 3390'),
  992.         ("MFG:Hewlett-Packard;MDL:PSC 2200 Series;CMD:MLC,PCL,PML,DW-PCL,DYN;"
  993.          "CLS:PRINTER;1284.4DL:4d,4e,1;", 0, "HP PSC 2210"),
  994.         ("MFG:HP;MDL:PSC 2200 Series;CLS:PRINTER;DES:PSC 2200 Series;",
  995.          1, "HP PSC 2210"),# from HPLIP
  996.         ("MFG:HEWLETT-PACKARD;MDL:DESKJET 990C;CMD:MLC,PCL,PML;CLS:PRINTER;"
  997.          "DES:Hewlett-Packard DeskJet 990C;", 0, "HP DeskJet 990C"),
  998.         ("CLASS:PRINTER;MODEL:HP LaserJet 6MP;MANUFACTURER:Hewlett-Packard;"
  999.          "DESCRIPTION:Hewlett-Packard LaserJet 6MP Printer;"
  1000.          "COMMAND SET:PJL,MLC,PCLXL,PCL,POSTSCRIPT;", 0, "HP LaserJet 6MP"),
  1001.         ("MFG:Canon;CMD:BJL,BJRaster3,BSCCe;SOJ:TXT01;MDL:iP3000;CLS:PRINTER;"
  1002.          "DES:Canon iP3000;VER:1.09;STA:10;FSI:03;", 1, "Canon PIXMA iP3000"),
  1003.         ("MFG:HP;MDL:Deskjet 5400 series;CMD:MLC,PCL,PML,DW-PCL,DESKJET,DYN;"
  1004.          "1284.4DL:4d,4e,1;CLS:PRINTER;DES:5440;", 1, "HP DeskJet 5440"),
  1005.         ("MFG:Hewlett-Packard;MDL:HP LaserJet 3390;"
  1006.          "CMD:PJL,MLC,PCL,POSTSCRIPT,PCLXL;",
  1007.          0, "HP LaserJet 3390.*Postscript"),
  1008.  
  1009.         # Generic models
  1010.         ("MFG:New;MDL:Unknown PS Printer;CMD:POSTSCRIPT;",
  1011.          2, "Generic postscript printer"),
  1012.         ("MFG:New;MDL:Unknown PCL6 Printer;CMD:PCLXL;", 2, "Generic PCL 6"),
  1013.         ("MFG:New;MDL:Unknown PCL5e Printer;CMD:PCL5e;", 2, "Generic PCL 5e"),
  1014.         ("MFG:New;MDL:Unknown PCL5c Printer;CMD:PCL5c;", 2, "Generic PCL 5c"),
  1015.         ("MFG:New;MDL:Unknown PCL5 Printer;CMD:PCL5;", 2, "Generic PCL 5"),
  1016.         ("MFG:New;MDL:Unknown PCL3 Printer;CMD:PCL;", 2, "Generic PCL"),
  1017.         ("MFG:New;MDL:Unknown ESC/P Printer;CMD:ESCP2E;", 2, "Generic ESC/P"),
  1018.         ("MFG:New;MDL:Unknown Printer;", 100, None),
  1019.         ]
  1020.  
  1021.     if stdin_deviceid:
  1022.         idlist = [(raw_input ('Device ID: '), 2, '')]
  1023.  
  1024.     all_passed = True
  1025.     for id, max_status_code, modelre in idlist:
  1026.         id_dict = parseDeviceID (id)
  1027.         (status, ppdname) = ppds.getPPDNameFromDeviceID (id_dict["MFG"],
  1028.                                                          id_dict["MDL"],
  1029.                                                          id_dict["DES"],
  1030.                                                          id_dict["CMD"])
  1031.         if status < max_status_code:
  1032.             success = True
  1033.         elif status == max_status_code:
  1034.             ppddict = ppds.getInfoFromPPDName (ppdname)
  1035.             match = re.match (modelre, ppddict['ppd-make-and-model'], re.I)
  1036.             success = match != None
  1037.         else:
  1038.             success = False
  1039.  
  1040.         if success:
  1041.             result = "PASS"
  1042.         else:
  1043.             result = "*** FAIL ***"
  1044.  
  1045.         print "%s: %s %s" % (result, id_dict["MFG"], id_dict["MDL"])
  1046.         all_passed = all_passed and success
  1047.  
  1048.     if not all_passed:
  1049.         raise RuntimeError
  1050.